//
// Created by fang.junpeng on 2017/6/7.
//

#include <Python.h>
#include "CppPy.h"
#include <stdexcept>

namespace dolphin{

    /*********PyRuntime Class********/
    PyRuntime::PyRuntime() {
        Py_Initialize();
        //check
        if (!Py_IsInitialized()){
            throw std::runtime_error("Python Interpret is not active!");
        }
    }
    PyRuntime::~PyRuntime() {
        Py_Finalize();
    }

    void PyRuntime::runPyString(std::string pyStr) {
        PyRun_SimpleString(pyStr.c_str());
    }

    void PyRuntime::runPyFile(std::string filename) {
        FILE *fp = fopen(filename.c_str(), "r");
        //protect the handle of file
        std::shared_ptr<FILE> guard(fp, [](FILE *fp){fclose(fp);});
        //run the python file
        PyRun_SimpleFile(guard.get(), filename.c_str());
    }

    void PyRuntime::add_path(std::string path) {
        std::string cmd = "import sys\n";
        cmd += "sys.path.append('"+path+"')";
        runPyString(cmd);
    }

    /*********PyWrap Class********/
    struct PyWrap{
        PyWrap(PyObject* obj):PyPtr(obj){}
        ~PyWrap(){
            if (nullptr != PyPtr){
                Py_DECREF(PyPtr);
            }
        }
        /**
         * judge the PyPtr is valid
         */
        bool valid(){
            return nullptr != PyPtr?true: false;
        }
        /**
         * judge the PyPtr is Callable
         */
        bool callable(){
            if (nullptr == PyPtr){
                return false;
            }
            return PyCallable_Check(PyPtr);
        }
        PyObject* PyPtr;
    };
    /*********PyObj Class********/
    PyObj::PyObj():_obj(nullptr) { }
    PyObj::PyObj(const PyObj &obj) {
        _obj = obj._obj;
    }
    PyObj::PyObj(PyWrap &&obj) {
        //new PyWrap and modify
        _obj.reset(new PyWrap(obj.PyPtr));
        obj.PyPtr = nullptr;
    }

    PyObj& PyObj::operator=(PyWrap &&wrap) {
        _obj.get()->PyPtr = wrap.PyPtr;//copy the PyPtr to the PyObj._obj
        wrap.PyPtr = nullptr;
        return *this;
    }

    PyObj& PyObj::operator=(const PyObj &obj) {
        if (_obj.get() != obj.get()) {
            _obj = obj._obj;//shared_ptr copy
        }
        return *this;
    }

    bool PyObj::valid() {
        if (nullptr == get()){
            return false;
        }
        return get()->valid();
    }

    bool PyObj::callable() {
        return valid()?_obj->callable(): false;
    }

    PyObj PyObj::call(PyObj *args, PyObj *kw) {
        Obj *arg[] {nullptr, nullptr};
        if (nullptr != args){ arg[0] = args->get();}
        if (nullptr != kw){ arg[1] = kw->get();}
        PyWrap wrap = PyObject_Call(get()->PyPtr,arg[0]->PyPtr, arg[1]->PyPtr);
        return PyObj(std::move(wrap));
    }

    PyObj::Obj* PyObj::get() const {
        return _obj.get();
    }

    /*********PyModule Class********/
    PyModule::PyModule(std::string module_name) {
        Import(module_name);
    }

    void PyModule::Import(std::string module_name) {
        PyWrap name = PyString_FromString(module_name.c_str());
        //load module
        PyWrap module = PyImport_Import(name.PyPtr);
        if (!module.valid()){
            throw std::runtime_error("can't find module:"+module_name);
        }
        //hold module Obj in shared_ptr
        PyObj::operator=(std::move(module));
    }

    PyObj PyModule::get_Attr(std::string attr) {
        PyWrap obj = PyObject_GetAttrString(_obj.get()->PyPtr,attr.c_str());
        if(!obj.valid()){
            PyErr_Print();
            throw std::runtime_error("can't find the attr:"+attr);
        }
        return PyObj(std::move(obj));
    }

    PyTuple::PyTuple(size_t size):_size(size) {
        PyWrap tmp = PyTuple_New(size);
        /**
         * copy tmp to self
         * PyObj is small, so this kind of copy is enough
         */
        PyObj::operator=(std::move(tmp));
    }

    void PyTuple::set_item(size_t index, PyObj& obj) {
        if(index >= _size){
            return;
        }
        PyTuple_SetItem(_obj.get()->PyPtr,index,obj.get()->PyPtr);
    }
}